#include <string.h>

#include "openssl/evp.h"
#include "openssl/aes.h"
#include "openssl/err.h"

#include "hq_aes.h"

static void handleErrors()
{
    ERR_print_errors_fp(stderr);
    abort();
}

const EVP_CIPHER* (*mode)() = MODE;

int HqAesEncStr(unsigned char *plaintext, int plaintext_len, unsigned char *key,
             unsigned char *iv, unsigned char *ciphertext)
{
    if (plaintext == NULL)
        return -1;
        
    EVP_CIPHER_CTX *ctx;
    int len;
    int ciphertext_len;

    if (!(ctx = EVP_CIPHER_CTX_new()))
        handleErrors();

    if (1 != EVP_EncryptInit_ex(ctx, mode(), NULL, key, iv))
        handleErrors();

    if (1 != EVP_CIPHER_CTX_set_padding(ctx, EVP_PADDING_PKCS7))
        handleErrors();

    if (1 != EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
        handleErrors();
    ciphertext_len = len;

    if (1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len))
        handleErrors();
    ciphertext_len += len;

    EVP_CIPHER_CTX_free(ctx);

    return ciphertext_len;
}


int HqAesDecStr(unsigned char *ciphertext, int ciphertext_len, unsigned char *key,
  unsigned char *iv, unsigned char *plaintext) {
  if (ciphertext == NULL)
      return -1;

  EVP_CIPHER_CTX *ctx;

  int len;
  int plaintext_len;

  if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

  if(1 != EVP_DecryptInit_ex(ctx, mode(), NULL, key, iv))
    handleErrors();

  if (1 != EVP_CIPHER_CTX_set_padding(ctx, EVP_PADDING_PKCS7))
    handleErrors();

  if(1 != EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
    handleErrors();
  plaintext_len = len;

  if(1 != EVP_DecryptFinal_ex(ctx, plaintext + len, &len)) handleErrors();
  plaintext_len += len;

  EVP_CIPHER_CTX_free(ctx);

  return plaintext_len;
}

int HqAesEncFile(const char *absPlainPath, unsigned char *key,
             unsigned char *iv, const char* absCipherPath) {
    unsigned char plain[MAX_FILE_LEN] = {0};
    unsigned char cipher[MAX_FILE_LEN] = {0};
    int ret = -1;

    FILE* fp = fopen(absPlainPath, "r");
    if (fp == NULL) {
        printf("AEC ENC FILE ERR!\n");
        ret = -1;
        goto exit;
    }

    int plain_len = fread(plain, 1, MAX_FILE_LEN + 1, fp);
    fclose(fp);

    if (ret == MAX_FILE_LEN + 1) {
        printf("AEC ENC FILE SIZE TOO BIG!\n");
        ret = -2;
        goto exit;
    }

    int cipher_len = HqAesEncStr(plain, plain_len, key, iv, cipher);

    fp = fopen(absCipherPath, "wb");
    if (fp == NULL) {
                printf("AEC ENC FILE ERR!\n");
        ret = -1;
        goto exit;
    }
    fwrite(cipher, 1, cipher_len, fp);
    fclose(fp);

    ret = 0;

exit:
    return ret;
}

int HqAesDecFile(const char * absCipherPath, unsigned char *key,
             unsigned char *iv, const char* absPlainPath) {
    unsigned char plain[MAX_FILE_LEN] = {0};
    unsigned char cipher[MAX_FILE_LEN] = {0};
    int ret = 0;

    FILE* fp = fopen(absCipherPath, "rb");
    if (fp == NULL) {
        printf("AEC DEC FILE ERR!\n");
        ret = -1;
        goto exit;
    }

    ret = fread(cipher, 1, MAX_FILE_LEN + 1, fp);
    fclose(fp);

    if (ret == MAX_FILE_LEN + 1) {
        printf("AEC DEC FILE SIZE TOO BIG!\n");
        ret = -2;
        goto exit;
    }

    int plain_len = HqAesDecStr(cipher, ret, key, iv, plain);

    fp = fopen(absPlainPath, "w");
    if (fp == NULL) {
        printf("AEC DEC FILE ERR!\n");
        ret = -1;
        goto exit;
    }
    fwrite(plain, 1, plain_len, fp);
    fclose(fp);

    ret = 0;

exit:
    return ret;
}